using System;
using System.Drawing;
using System.Linq;
using System.Collections.Generic;

namespace TicTacToe
{
    /// <summary>
    /// Holds the state of every cell on the board and provides easy access to rows, columns and diagonals.
    /// Location (0, 0) is the top-left corner of the board.
    /// </summary>
    public class Board
    {
        private const int BoardSize = 3;

        /// <summary>
        /// Stores the current state of the board. Each inner array represents a row of symbols.
        /// Board[1][2] represents row 1, column 2.
        /// </summary>
        private Symbol?[][] _board;

        /// <summary>
        /// Internal contructor for testing. 
        /// </summary>
        /// <param name="state">
        /// Creates a board with a state specified by a string of characters, one for each space on the board.
        /// The string should comprise only 'x', 'o', or ' ', representing crosses, noughts and empty spaces respectively.
        ///  </param>
        internal Board(string state)
        {
            _board = BoardArrayFactory.CreateBoardArrayFromState(state, BoardSize);
        }

        public Board()
        {
            Reset();
        }

        public int Size { get { return BoardSize; } }
        
        public bool IsFull()
        {
            return _board.All(row => row.All(cell => cell.HasValue));
        }

        public void Reset()
        {
            _board = BoardArrayFactory.CreateBoardArray(BoardSize); 
        }

        public Symbol? GetValue(Point location)
        {
            return _board[location.X][location.Y];
        }

        public void SetValue(Point location, Symbol value)
        {
            _board[location.X][location.Y] = value;
        }

        public IEnumerable<IEnumerable<Cell>> GetAllScoringCellLines()
        {
            foreach (var line in GetAllRows().Concat(GetAllColumns()))
            {
                yield return line;
            }
            yield return GetCellsOnForwardDiagonal();
            yield return GetCellsOnBackwardsDiagonal();
        } 

        public IEnumerable<IEnumerable<Cell>> GetAllColumns()
        {
            return FromZeroToBoardSize(GetCellsInColumn);
        }

        public IEnumerable<IEnumerable<Cell>> GetAllRows()
        {
            return FromZeroToBoardSize(GetCellsInRow);
        }

        public IEnumerable<Cell> GetCellsInRow(int rowIndex)
        {
            return FromZeroToBoardSize(columnIndex => new Cell(_board[rowIndex][columnIndex], rowIndex, columnIndex));
        }

        public IEnumerable<Cell> GetCellsInColumn(int columnIndex)
        {
            return FromZeroToBoardSize(rowIndex => new Cell(_board[rowIndex][columnIndex], rowIndex, columnIndex));
        }

        //Backwards diagonal contains indices (0,0);(1,1);(2,2)
        public IEnumerable<Cell> GetCellsOnBackwardsDiagonal()
        {
            return FromZeroToBoardSize(index => new Cell(_board[index][index], index, index));
        }

        //Forwards diagonal contains indices (0,2);(1,1);(2,0)
        //We need an increasing row index and decreasing column index to pick these out
        public IEnumerable<Cell> GetCellsOnForwardDiagonal()
        {
            var columnIndex = BoardSize;
            for (var rowIndex = 0; rowIndex < BoardSize; rowIndex++)
            {
                columnIndex--;
                yield return new Cell(_board[rowIndex][columnIndex], rowIndex, columnIndex);
            }
        }

        private static IEnumerable<T> FromZeroToBoardSize<T>(Func<int, T> action)
        {
            for (var i = 0; i < BoardSize; i++)
            {
                yield return action(i);
            }
        }
    }
}